package cn.kkdlk.transform;

import org.locationtech.proj4j.*;

public class CoordinateTransformUtils {

    private static final CRSFactory mCrsFactory = new CRSFactory();

    private CoordinateTransformUtils() {
    }

    /**
     * 坐标 wgs84 转 cgcs2000
     *
     * @param sourceCoordinate 源坐标
     * @param signed           带号
     * @return
     */
    public static ProjCoordinate wgs84ToCGCS2000BySigned(ProjCoordinate sourceCoordinate, int signed) {
        CoordinateReferenceSystem sourceCRS = mCrsFactory.createFromParameters("WGS84", "+proj=longlat +datum=WGS84 +no_defs");
        CoordinateReferenceSystem targetCRS = createFrom2000Parameters(signed);
        CoordinateTransformFactory ctf = new CoordinateTransformFactory();
        CoordinateTransform transform = ctf.createTransform(sourceCRS, targetCRS);
        ProjCoordinate targetCoordinate = new ProjCoordinate(0.0, 0.0, 0.0);
        transform.transform(sourceCoordinate, targetCoordinate);
        targetCoordinate.z = sourceCoordinate.z;
        return targetCoordinate;
    }

    /**
     * 坐标 wgs84 转 cgcs2000
     *
     * @param sourceCoordinate 源坐标
     * @param L0               中央经线
     * @param bandWidth        带宽
     * @return
     */
    public static ProjCoordinate wgs84ToCGCS2000ByL0AndBandWidth(ProjCoordinate sourceCoordinate, int L0, int bandWidth) {
        CoordinateReferenceSystem sourceCRS = mCrsFactory.createFromParameters("WGS84", "+proj=longlat +datum=WGS84 +no_defs");
        //计算分带号
        int signed = generateSignedByL0AndBandWidth(L0, bandWidth);
//        log.info("中央经线:{};分带号:{};带宽:{}", L0, signed, bandWidth);
        CoordinateReferenceSystem targetCRS = createFrom2000Parameters(signed, bandWidth);
        CoordinateTransformFactory ctf = new CoordinateTransformFactory();
        CoordinateTransform transform = ctf.createTransform(sourceCRS, targetCRS);
        ProjCoordinate targetCoordinate = new ProjCoordinate(0.0, 0.0, 0.0);
        transform.transform(sourceCoordinate, targetCoordinate);
        targetCoordinate.z = sourceCoordinate.z;
        return targetCoordinate;
    }


    /**
     * 坐标cgcs2000 转 wgs84
     *
     * @param sourceCoordinate 源坐标
     * @param L0               中央经线
     * @param bandWidth        带宽 3 6
     * @return
     */
    public static ProjCoordinate cgcs2000ToWGS84ByL0BandWidth(ProjCoordinate sourceCoordinate, int L0, int bandWidth) {
        // 带号
        int signed = L0 / bandWidth;
        String xBeforeDecimalPoint = String.format("%.8f", sourceCoordinate.x);
        xBeforeDecimalPoint = xBeforeDecimalPoint.contains(".") ? xBeforeDecimalPoint.substring(0, xBeforeDecimalPoint.lastIndexOf(".")) : xBeforeDecimalPoint;
        // 没带带号是6位带了是8位
        if (xBeforeDecimalPoint.length() >= 8 && xBeforeDecimalPoint.startsWith(String.valueOf(signed))) {
            throw new RuntimeException("错误，当前经度可能携带带号，请重新选择!");
        }
        CoordinateReferenceSystem sourceCRS = createFrom2000ParametersByL0(L0, bandWidth);
        CoordinateReferenceSystem targetCRS = mCrsFactory.createFromParameters("WGS84", "+proj=longlat +datum=WGS84 +no_defs"); // 正常经纬度坐标系
        CoordinateTransformFactory ctf = new CoordinateTransformFactory();
        CoordinateTransform transform = ctf.createTransform(sourceCRS, targetCRS);
        ProjCoordinate targetCoordinate = new ProjCoordinate(0.0, 0.0, 0.0);
        transform.transform(sourceCoordinate, targetCoordinate);
        targetCoordinate.z = sourceCoordinate.z;
        return targetCoordinate;
    }

    /**
     * 坐标cgcs2000 转 wgs84
     *
     * @param sourceCoordinate 源坐标
     * @param signed           带号
     * @param bandWidth        带宽
     * @return
     */
    public static ProjCoordinate cgcs2000ToWGS84BySingedBandWidth(ProjCoordinate sourceCoordinate, int signed, int bandWidth) {
        CoordinateReferenceSystem sourceCRS = createFrom2000Parameters(signed, bandWidth);
        CoordinateReferenceSystem targetCRS = mCrsFactory.createFromParameters("WGS84", "+proj=longlat +datum=WGS84 +no_defs"); // 正常经纬度坐标系
        CoordinateTransformFactory ctf = new CoordinateTransformFactory();
        CoordinateTransform transform = ctf.createTransform(sourceCRS, targetCRS);
        ProjCoordinate targetCoordinate = new ProjCoordinate(0.0, 0.0, 0.0);
        transform.transform(sourceCoordinate, targetCoordinate);
        targetCoordinate.z = sourceCoordinate.z;
        return targetCoordinate;
    }


    /***
     * WGS84转CGCS2000，根据带宽和wgs84经纬度转换为cgcs2000
     * @param sourceCoordinate 源坐标
     * @param bandWidth 带宽【3 或 6】
     * @return
     */
    public static ProjCoordinate wgs84ToCGCS2000(ProjCoordinate sourceCoordinate, int bandWidth) {

        CoordinateReferenceSystem sourceCRS = mCrsFactory.createFromParameters("WGS84", "+proj=longlat +datum=WGS84 +no_defs");
        // 获取wgs84经度
        double lon = sourceCoordinate.x;
        //计算中央经线
        int LO = generateCentralMeridianByLng(lon, bandWidth);
        //计算分带号
        int signed = generateSignedByL0AndBandWidth(LO, bandWidth);
//        log.info("中央经线:{};分带号:{}", LO, signed);
        // 根据分带号，带宽生成 cgcs2000 投影参数
        CoordinateReferenceSystem targetCRS = createFrom2000Parameters(signed, bandWidth);

        CoordinateTransformFactory ctf = new CoordinateTransformFactory();
        CoordinateTransform transform = ctf.createTransform(sourceCRS, targetCRS);
        ProjCoordinate targetCoordinate = new ProjCoordinate(0.0, 0.0, 0.0);
        // 投影转换
        transform.transform(sourceCoordinate, targetCoordinate);
        targetCoordinate.z = sourceCoordinate.z;
        return targetCoordinate;
    }


    /***
     * WGS84转CGCS2000，根据带宽和wgs84经纬度转换为cgcs2000，不带带号
     * @param sourceCoordinate 源坐标
     * @param bandWidth 带宽【3 或 6】
     * @return
     */
    public static ProjCoordinate wgs84ToCGCS2000NoSigned(ProjCoordinate sourceCoordinate, int bandWidth) {
        CoordinateReferenceSystem sourceCRS = mCrsFactory.createFromParameters("WGS84", "+proj=longlat +datum=WGS84 +no_defs");
        // 获取wgs84经度
        double lon = sourceCoordinate.x;
        //计算中央经线
        int LO = generateCentralMeridianByLng(lon, bandWidth);
        //计算分带号
        int signed = generateSignedByL0AndBandWidth(LO, bandWidth);
//        log.info("中央经线:{};分带号:{}", LO, signed);
        // 根据分带号，带宽生成 cgcs2000 投影参数
        CoordinateReferenceSystem targetCRS = createFrom2000ParametersNoSigned(signed, bandWidth);
        CoordinateTransformFactory ctf = new CoordinateTransformFactory();
        CoordinateTransform transform = ctf.createTransform(sourceCRS, targetCRS);
        ProjCoordinate targetCoordinate = new ProjCoordinate(0.0, 0.0, 0.0);
        // 投影转换
        transform.transform(sourceCoordinate, targetCoordinate);
        targetCoordinate.z = sourceCoordinate.z;
        return targetCoordinate;
    }


    /**
     * 根据带宽,wgs84经度，计算中央子午线
     *
     * @param longitude 经度
     * @param bandWidth 带宽[3,6]
     * @return 中央子午线
     */
    public static int generateCentralMeridianByLng(double longitude, int bandWidth) {
        return (int) Math.round(longitude / bandWidth) * bandWidth;
    }


    /**
     * 根据中央子午线和带宽计算带号
     *
     * @param L0        中央子午线
     * @param bandWidth 带宽
     * @return 带号
     */
    public static int generateSignedByL0AndBandWidth(int L0, int bandWidth) {
        // double bandNum = 0;
        // if ("三度带".equals(step)) {
        //     bandNum = L0 / 3;
        // } else if ("六度带".equals(step)) {
        //     bandNum = (L0 + 3) / 6;
        // }
        return Math.round((float) (L0 + (bandWidth - 3)) / bandWidth);
    }


    /***
     * 根据带号获取 投影参数
     * @param signed 带号
     * @return cgcs2000 投影参数
     * @throws RuntimeException 带号不在带宽【3,6】的范围内就抛出错误
     */
    public static CoordinateReferenceSystem createFrom2000Parameters(int signed) throws RuntimeException {
        if (signed >= 24 && signed <= 45) {
            //三度带
            return createFrom2000Parameters(signed, 3);
        } else if (signed >= 13 && signed <= 23) {
            //六度带
            return createFrom2000Parameters(signed, 6);
        } else {
//            log.error("带号错误，不在中国范围内，无法得知其分带,错误分带为： {}", signed);
            throw new RuntimeException("带号错误，不在中国范围内，无法得知其分带");
        }
    }


    /**
     * 根据带号，带宽创建cgcs2000 投影参数
     * 带号 * 带宽 = 中央经线
     *
     * @param signed    带号
     * @param bandWidth 带宽
     * @return cgcs2000 投影参数
     */
    public static CoordinateReferenceSystem createFrom2000Parameters(int signed, int bandWidth) {
        String param = String.format("+proj=tmerc +lat_0=0 +lon_0=%s +k=1 +x_0=%s500000 +y_0=0 +ellps=GRS80 +units=m +no_defs", signed * bandWidth, signed);
        return mCrsFactory.createFromParameters("CGCS2000", param);
    }

    /**
     * 根据带号，带宽创建cgcs2000 投影参数
     * 带号 * 带宽 = 中央经线
     *
     * @param signed    带号
     * @param bandWidth 带宽
     * @return cgcs2000 投影参数
     */
    public static CoordinateReferenceSystem createFrom2000ParametersNoSigned(int signed, int bandWidth) {
        String param = String.format("+proj=tmerc +lat_0=0 +lon_0=%s +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs", signed * bandWidth);
        return mCrsFactory.createFromParameters("CGCS2000NoSigned", param);
    }


    /**
     * 已知中央经线，和带宽，创建cgcs2000 投影参数
     *
     * @param L0 中央经线
     * @return cgcs2000 投影参数
     */
    public static CoordinateReferenceSystem createFrom2000ParametersByL0(int L0, int bandWidth) {
        String param = "";
        if (bandWidth == 3) {
            param = String.format("+proj=tmerc +lat_0=0 +lon_0=%s +k=1 +x_0=500000  +y_0=0 +ellps=GRS80 +units=m +no_defs", L0);
        } else if (bandWidth == 6) {
            param = String.format("+proj=tmerc +lat_0=0 +lon_0=%s +k=1 +x_0=500000  +y_0=0 +ellps=GRS80 +units=m +no_defs", L0);
        } else {
            throw new RuntimeException("非合法带宽");
        }
        return mCrsFactory.createFromParameters("CGCS2000", param);
    }
}
